/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package skyproc;

import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JCheckBox;
import javax.swing.JOptionPane;
import skyproc.gui.SPProgressBarPlug;

/**
 *
 * @author pc tech
 */
public class SkyBash {

    Map<JCheckBox, Boolean> guiButtons = new HashMap<>();
    Map settingsMap;
    Map<String, String> modTagsMap;
    //Stores a map of a mods tags with the mod.  Passed to merger classes via public.
    HashMap<Mod, ModTags> mods = new HashMap<>();
    static ModTags mTags;
    //Number of progress steps.  numSteps is overridden with the number of mods to parse.
    static int numSteps = 5;
    static int steps = 0;
    //Full logging boolean for neatness in below code.
    public static boolean fullLogging;
    //Mod name for use in merger function logging.
    public static String currentMod;
    //Record name for use in merger function logging.
    public static String currentRecord;
    //Storage mod used in counter.
    Mod temp;
    //Lists forms w/ NPC's.
    HashMap<FormID, MajorRecord> list = new HashMap<>();
    //Lists all the base NPC's from the Skyrim.esm, so base data isn't considered changed at any point.
    HashMap<FormID, MajorRecord> baseList = new HashMap<>();
    //Storage map for lists of removed LVLO's from a base leveled list.
    public static HashMap<FormID, LVLOList> leveledRecordMap = new HashMap<>();

    public void SkyBashMerger(boolean fullLogging) {

        MajorRecord m;
        MajorRecord newM;
        MajorRecord baseM;

        //Set full logging.
        SkyBash.fullLogging = fullLogging;

        //Progress bar update.
        SPProgressBarPlug.setStatus("Processing Base Records");
        SPProgressBarPlug.incrementBar();

        //Iterate through mod lists and grab base records.
        for (GRUP g : SPGlobal.getDB().getMod(new ModListing("Skyrim", true)).GRUPs.values()) {
            for (Object o : g.listRecords) {
                m = (MajorRecord) o;
                baseList.put(m.getForm(), m);
            }
        }
        for (GRUP g : SPGlobal.getDB().getMod(new ModListing("Update", true)).GRUPs.values()) {
            for (Object o : g.listRecords) {
                m = (MajorRecord) o;
                baseList.put(m.getForm(), m);
            }
        }

        //Iterate through the mods, starting after Skyrim/Update masters.
        for (int i = SPGlobal.getDB().modIndex(new ModListing("Skyrim", true)) + 2; i < SPGlobal.getDB().numMods(); i++) {

            //Grab the mod being used.
            temp = SPGlobal.getDB().getMod(i);
            SkyBash.currentMod = temp.getNameNoSuffix();

            //Update progress bar with the mod it is processing.
            SPProgressBarPlug.setStatus("Processing Records from " + temp.getName());
            SPProgressBarPlug.incrementBar();

            //Get the mod tags for the merger.
            if (mods != null && !mods.isEmpty() && mods.containsKey(temp)) {
                mTags = mods.get(temp);
            } else {
                mTags = new ModTags(true);
            }

            //If the tags say to deactivate, it skips that mod.
            if (!mTags.Deactivate) {
                //Iterates through the GRUP's for each mod.
                for (GRUP g : temp.GRUPs.values()) {
                    //Iterates through the records for each mod.
                    for (Object o : g.listRecords) {
                        //Casts to a MajorRecord as listRecords returns Objects.  Silly java.
                        m = (MajorRecord) o;
                        //If it's the first instance of the NPC, adds to list and leaves it alone.
                        // Base NPC's are assumed overwritten, so not counted here.
                        if (!list.containsKey(m.getForm())) {
                            list.put(m.getForm(), m);
                        } else {
                            //Grab the record off the list if it's already there, so the new ones can be merged.
                            newM = list.get(m.getForm());

                            if (baseList.containsKey(m.getForm())) {
                                baseM = baseList.get(m.getForm());
                            } else {
                                baseM = m;
                            }
                            SPGlobal.log(g.toString() + " MERGER: ", "Merging " + m.getEDID() + " from " + temp.getName());
                            SkyBash.currentRecord = m.getEDID();
                            m.merge(newM, baseM);
                            SPGlobal.getGlobalPatch().addRecord(m);
                        }
                    }
                }
            }
        }
    }

    public static void logMerge(String type, String o) {
        SPGlobal.log(type + ": ", "Merged " + type + " to " + o + " for "
                + SkyBash.currentRecord + " from Mod " + SkyBash.currentMod);
    }

    public static void logMerge(String customHeader, String type, String o) {
        SPGlobal.log(customHeader + ": ", "Merged " + type + " for "
                + SkyBash.currentRecord + " from Mod " + SkyBash.currentMod);
    }

    public static void logMerge(String type, SubFormData sfi) {
        SPGlobal.log(type + ": ", "Merged data for " + type + " for ID " + sfi.ID + " to "
                + SkyBash.currentRecord + " from Mod " + SkyBash.currentMod);
    }

    public static float merge(float f, float newfloat, float basefloat, Type type, String s) {
        if (f != newfloat && newfloat != basefloat) {
            f = newfloat;
            if (SkyBash.fullLogging) {
                SPGlobal.log(type.toString() + ": ", "Merged " + s + " to " + f + " for "
                        + SkyBash.currentRecord + " from Mod " + SkyBash.currentMod);
            }
        }
        return f;
    }

    public static int merge(int integer, int newint, int baseint, Type type, String s) {
        if (integer != newint && newint != baseint) {
            integer = newint;
            if (SkyBash.fullLogging) {
                SPGlobal.log(type.toString() + ": ", "Merged " + s + " to " + integer + " for "
                        + SkyBash.currentRecord + " from Mod " + SkyBash.currentMod);
            }
        }
        return integer;
    }

    public static byte[] merge(byte[] d, byte[] newd, byte[] based, Type type, String s) {
        if (!Arrays.equals(d, newd) && !Arrays.equals(newd, based)) {
            d = newd;
            if (SkyBash.fullLogging) {
                SPGlobal.log(type.toString() + ": ", "Merged " + s + " to " + d + " for "
                        + SkyBash.currentRecord + " from Mod " + SkyBash.currentMod);
            }
        }
        return d;
    }

    public static Object merge(Object o, Object no, Object bo, Type type, String s) {
        if (!o.equals(no) && !no.equals(bo)) {
            o = no;
            if (SkyBash.fullLogging) {
                SPGlobal.log(type.toString() + ": ", "Merged " + s + " to " + o + " for "
                        + SkyBash.currentRecord + " from Mod " + SkyBash.currentMod);
            }
        }
        return o;
    }

    public static Enum merge(Enum e, Enum ne, Enum be, Type type, String s) {
        if (!e.equals(ne) && !ne.equals(be)) {
            e = ne;
            if (SkyBash.fullLogging) {
                SPGlobal.log(type.toString() + ": ", "Merged " + s + " to " + e + " for "
                        + SkyBash.currentRecord + " from Mod " + SkyBash.currentMod);
            }
        }
        return e;
    }

    public static boolean merge(boolean b, boolean nb, boolean bb, Type type, String s) {
        if (b != nb && !nb != bb) {
            b = nb;
            if (SkyBash.fullLogging) {
                SPGlobal.log(type.toString() + ": ", "Merged " + s + " to " + b + " for "
                        + SkyBash.currentRecord + " from Mod " + SkyBash.currentMod);
            }
        }
        return b;
    }

    public static String merge(String so, String ns, String bs, Type type, String s) {
        if (!so.equals(ns) && !ns.equals(bs)) {
            so = ns;
            if (SkyBash.fullLogging) {
                SPGlobal.log(type.toString() + ": ", "Merged " + type.toString() + " to " + s + " for "
                        + SkyBash.currentRecord + " from Mod " + SkyBash.currentMod);
            }
        }
        return so;
    }

    /**
     * Merges ArrayLists with logging capabilities, from SubLists.
     *
     * @param tempListNew The new ArrayList to be merged.
     * @param tempListBase The base ArrayList, to prevent base data from being
     * re-merged.
     * @return The modified ArrayList.
     */
    public static <T extends Object> ArrayList<T> merge(ArrayList<T> tempList, ArrayList<T> tempListNew, ArrayList<T> tempListBase, Type type) {
        if (!tempList.equals(tempListNew) && !tempListBase.equals(tempListNew)) {
            for (int i = 0; i < tempListBase.size(); i++) {
                T t = tempListBase.get(i);
                if ((!tempListNew.contains(t) && tempList.contains(t))) {
                    tempList.remove(t);
                    if (SkyBash.fullLogging) {
                        SPGlobal.log(type.toString() + ": ", "Removed " + type.toString() + " " + t + " to "
                                + SkyBash.currentRecord + " from Mod " + SkyBash.currentMod);
                    }
                }
            }
            for (int i = 0; i < tempListNew.size(); i++) {
                T t = tempListNew.get(i);
                if (!tempList.contains(t) && !tempListBase.contains(t)) {
                    tempList.add(t);
                    if (SkyBash.fullLogging) {
                        SPGlobal.log(type.toString() + ": ", "Merged " + type.toString() + " " + t + " to "
                                + SkyBash.currentRecord + " from Mod " + SkyBash.currentMod);
                    }
                }
            }
        }
        return tempList;
    }

    private void processBossMasterlist() {
        String currentLine;
        String nextLine;
        HashMap<String, Mod> modList;

        File f = new File("BashTags.txt");
        if (f.isFile() && f.canRead()) {
            try {
                BufferedReader parser = new BufferedReader(new FileReader(f));
                try {
                    modList = new HashMap<>();
                    for (int i = 0; i < SPGlobal.getDB().numMods(); i++) {
                        temp = SPGlobal.getDB().getMod(i);
                        modList.put(temp.getName(), temp);
                    }
                    while ((currentLine = parser.readLine()) != null) {
                        if (modList.containsKey(currentLine.trim()) && !mods.containsKey(modList.get(currentLine))) {
                            nextLine = parser.readLine();
                            if (nextLine.contains("TAG:")) {
                                nextLine = nextLine.substring(nextLine.indexOf("BASH: ") + 6, nextLine.indexOf("}}"));
                                mods.put(modList.get(currentLine), new ModTags(nextLine));
                            }
                        }
                    }
                } catch (IOException ex) {
                    JOptionPane.showMessageDialog(null, "Invalid masterlist.  Please locate the BOSS masterlist and "
                            + "correct the entry in the SkyBash.ini appropriately.");
                    Logger.getLogger(SkyBash.class.getName()).log(Level.SEVERE, null, ex);
                }
            } catch (FileNotFoundException ex) {
                JOptionPane.showMessageDialog(null, "Invalid masterlist.  Please locate the BOSS masterlist and "
                        + "correct the entry in the SkyBash.ini appropriately.");
                Logger.getLogger(SkyBash.class.getName()).log(Level.SEVERE, null, ex);
            }
        } else {
            JOptionPane.showMessageDialog(null, "Invalid masterlist.  Please locate the BOSS masterlist and "
                    + "correct the entry in the SkyBash.ini appropriately.");
        }
    }
}

/**
 * Non-public class intended to store bash tags for mods.
 *
 * @author Kurtis
 */
class ModTags {

    //Generic deactivate tag.
    boolean Deactivate;
    //All initially declared false until import statements are processed.
    //Actor booleans.
    boolean ActorACBS = false;
    boolean ActorAIDT = false;
    boolean ActorAIPackages = false;
    boolean ActorAIPackagesForceAdd = false;
    boolean ActorAnimations = false;
    boolean ActorCombatStyle = false;
    boolean ActorDeathItem = false;
    boolean ActorSpells = false;
    boolean ActorSpellsForceAdd = false;
    boolean ActorStats = false;
    boolean ActorSkills = false;
    boolean ActorFactions = false;
    boolean ActorInventories = false;
    boolean ActorFaces = false;
    boolean ActorClass = false;
    boolean ActorEyesOnly = false;
    boolean ActorHairOnly = false;
    boolean ActorRace = false;
    boolean ActorFlags = false;
    boolean ActorTemplateFlags = false;
    boolean ActorPerks = false;
    boolean ActorOutfits = false;
    boolean ActorSkin = false;
    boolean ActorTemplates = false;
    boolean ActorVoice = false;
    //Race tags
    boolean RStatsF = false;
    boolean RStatsM = false;
    boolean BodySizeF = false;
    boolean BodySizeM = false;
    boolean BodyF = false;
    boolean BodyM = false;
    boolean eyes = false;
    boolean Hair = false;
    boolean RSpells = false;
    boolean REars = false;
    boolean RHead = false;
    boolean VoiceF = false;
    boolean VoiceM = false;
    boolean RBody = false;
    //More universal tags.
    boolean keywords = false;
    boolean scripts = false;
    boolean names = false;
    //Graphics tags.
    boolean Graphics = false;
    //Sounds
    boolean Sounds = false;
    boolean Stats = false;
    //Leveled Lists
    boolean Delev = false;
    //Data lock for imports, to lock and prevent anything from overriding it.  Normally off.
    boolean lockData = false;

    /**
     * Specialized constructor for tags.
     *
     * @param tags Constructor for a string list of tags, in the form of tag1,
     * tag2, tag3.. etc.
     */
    public ModTags(String tags) {
        Scanner s = new Scanner(tags);
        s.useDelimiter(",");

        while (s.hasNext()) {
            switch (s.next().trim()) {
                case "DEACTIVATE":
                    Deactivate = true;
                    break;
                case "Actor.ACBS":
                    ActorACBS = true;
                    break;
                case "Actor.AIDT":
                    ActorAIDT = true;
                    break;
                case "Actor.AIPackages":
                    ActorAIPackages = true;
                    break;
                case "Actor.AIPackagesForceAdd":
                    ActorAIPackagesForceAdd = true;
                    break;
                case "Actor.Animations":
                    ActorAnimations = true;
                    break;
                case "Actor.CombatStyle":
                    ActorCombatStyle = true;
                    break;
                case "Actor.DeathItem":
                    ActorDeathItem = true;
                    break;
                case "Actor.Spells":
                    ActorSpells = true;
                    break;
                case "Actor.SpellsForceAdd":
                    ActorSpellsForceAdd = true;
                    break;
                case "Actor.Stats":
                    ActorStats = true;
                    break;
                case "Actor.Skills":
                    ActorSkills = true;
                    break;
                case "Factions":
                    ActorFactions = true;
                    break;
                case "Invent":
                    ActorInventories = true;
                    break;
                case "NpcFaces":
                    ActorFaces = true;
                    break;
                case "names":
                    names = true;
                    break;
                case "NPC.Class":
                    ActorClass = true;
                    break;
                case "Npc.EyesOnly":
                    ActorEyesOnly = true;
                    break;
                case "Npc.HairOnly":
                    ActorHairOnly = true;
                    break;
                case "Scripts":
                    scripts = true;
                    break;
                case "Keywords":
                    keywords = true;
                    break;
                case "Actor.Flags":
                    ActorFlags = true;
                    break;
                case "Actor.TemplateFlags":
                    ActorTemplateFlags = true;
                    break;
                case "Actor.Perks":
                    ActorPerks = true;
                    break;
                case "Actor.Outfits":
                    ActorOutfits = true;
                    break;
                case "Actor.Skin":
                    ActorSkin = true;
                    break;
                case "Actor.Templates":
                    ActorTemplates = true;
                    break;
                case "Actor.Voices":
                    ActorVoice = true;
                    break;
                case "LockData":
                    lockData = true;
                    break;
                case "Sounds":
                    Sounds = true;
                    break;
                case "Stats":
                    Stats = true;
                    break;
                case "R.Attributes-F":
                    RStatsF = true;
                    break;
                case "R.Attributes-M":
                    RStatsM = true;
                    break;
                case "Body-Size-F":
                    BodySizeF = true;
                    break;
                case "Body-Size-M":
                    BodySizeM = true;
                    break;
                case "Body-F":
                    BodyF = true;
                    break;
                case "Body-M":
                    BodyM = true;
                    break;
                case "Eyes":
                    eyes = true;
                    break;
                case "Hair":
                    Hair = true;
                    break;
                case "R.AddSpells":
                    RSpells = true;
                    break;
                case "R.Ears":
                    REars = true;
                    break;
                case "R.Head":
                    RHead = true;
                    break;
                case "Voice-F":
                    VoiceF = true;
                    break;
                case "Voice-M":
                    VoiceM = true;
                    break;
                case "R.Body":
                    RBody = true;
                    break;
                case "Delev":
                    Delev = true;
                    break;
            }

            s.close();
        }
    }

    /**
     * Default mod tags constructor.
     *
     * @param use Constructor for activating all tags but deactivate and
     * override. Default for most mods.
     */
    public ModTags(boolean use) {
        if (use) {
            ActorACBS = true;
            ActorAIDT = true;
            ActorAIPackages = true;
            ActorAIPackagesForceAdd = true;
            ActorAnimations = true;
            ActorCombatStyle = true;
            ActorDeathItem = true;
            ActorSpells = true;
            ActorSpellsForceAdd = true;
            ActorStats = true;
            ActorSkills = true;
            ActorFactions = true;
            ActorInventories = true;
            ActorFaces = true;
            ActorClass = true;
            ActorEyesOnly = true;
            ActorHairOnly = true;
            ActorRace = true;
            ActorFlags = true;
            ActorTemplateFlags = true;
            ActorPerks = true;
            ActorOutfits = true;
            ActorSkin = true;
            ActorTemplates = true;
            ActorVoice = true;

            keywords = true;
            scripts = true;
            names = true;
            Graphics = true;
            Sounds = true;
            Stats = true;

            lockData = false;
            Deactivate = false;

            RStatsF = true;
            RStatsM = true;
            BodySizeF = true;
            BodySizeM = true;
            BodyF = true;
            BodyM = true;
            eyes = true;
            Hair = true;
            RSpells = true;
            REars = true;
            RHead = true;
            VoiceF = true;
            VoiceM = true;
            RBody = true;

            Delev = true;
        }
    }
}
